/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package drexel.cs544.nap.client;
import java.io.*;
import java.net.*;
import java.nio.*;
import drexel.cs544.nap.NAP;
import org.apache.log4j.Logger;

/**
 *
 * @author rch_ltp
 */
public class NAPClient {

    private static NAPClient _instance;
    private static final int PORT_NUMBER = 5440;
    private Socket _sServer;
    private int _clientID;
    private int _serverVersion;
    private int _maxPacketSize;
    private int _windowSize;
    private int _groupID;
   // private static ByteBuffer _msgOutBuff;

    public NAPClient()
    {
        _clientID = (int)(Math.random()*100);
        _sServer = null;
        _maxPacketSize = 1024;
        _windowSize = 100;
    }

    public static NAPClient getInstance()
    {
        if (_instance == null)
            _instance = new NAPClient();
        return _instance;
    }
    
    public void connect(String hostname) throws Exception
    {
        Socket sServer = null;
	try
	{
            sServer = new Socket(hostname,PORT_NUMBER);
	}
	catch (UnknownHostException e) {
            System.err.println("Incorrect host: " + hostname + ".");
            e.printStackTrace();
            System.exit(-1);
        }
        catch (IOException e) {
            System.err.println("Could not connect to " + hostname + ".");
            e.printStackTrace();
            System.exit(-1);
        }

        OutputStream out = sServer.getOutputStream(); //write to server
        InputStream in = sServer.getInputStream();   //read from server

        // LISTEN STATE

        // Send Connection Request

        ByteBuffer msgOutBuff = ByteBuffer.allocate(1024);
        byte[] msgBuff = new byte[1024];
        msgOutBuff.clear();
        msgOutBuff.put(NAP.Message.MSG_CLIENT_CONNECTION_REQUEST.Value());
        msgOutBuff.putInt(1);
        msgOutBuff.putInt(_clientID);
        msgOutBuff.putInt(_maxPacketSize);
        msgOutBuff.putInt(_windowSize);

        out.write(msgOutBuff.array(), 0, NAP.Message.MSG_CLIENT_CONNECTION_REQUEST.Length());

        // Receive Connection Accept
        int msgLen = in.read(msgBuff, 0, NAP.Message.MSG_CLIENT_CONNECTION_ACCEPT.Length());

        if (msgLen == NAP.Message.MSG_CLIENT_CONNECTION_ACCEPT.Length()
                && msgBuff[0] == NAP.Message.MSG_CLIENT_CONNECTION_ACCEPT.Value()) {

            // Accept message received
            _serverVersion = byteArrayToInt(msgBuff, 1);
            _maxPacketSize = byteArrayToInt(msgBuff, 5);
            _windowSize = byteArrayToInt(msgBuff, 9);

            // Get the next message
            msgLen = in.read(msgBuff, 0, NAP.Message.MSG_SERVER_REQUEST_TO_ADD_CLIENT.Length());

            if (msgLen == NAP.Message.MSG_SERVER_REQUEST_TO_ADD_CLIENT.Length()
                    && msgBuff[0] == NAP.Message.MSG_SERVER_REQUEST_TO_ADD_CLIENT.Value()) {

                // Check Host Key and ACK
                int hostKey = byteArrayToInt(msgBuff, 5);
                _groupID = byteArrayToInt(msgBuff, 9);
                int reqID = byteArrayToInt(msgBuff, 13);

                Logger.getLogger(NAPClient.class).info("Connecting to Server (hostkey): "
                        + hostKey + " Version: "
                        + _serverVersion + " As Group: " + _groupID);

                // Send ACK
                msgOutBuff.clear();
                msgOutBuff.put(NAP.Message.MSG_CLIENT_ADD_ACK.Value());
                msgOutBuff.putInt(reqID);
                out.write(msgOutBuff.array(), 0, NAP.Message.MSG_CLIENT_ADD_ACK.Length());
            }
        }
        else {
            // Connection Error
            Logger.getLogger(NAPClient.class).info("Error connecting to server: " + hostname);
        }

        // END LISTEN

        // CONFIG STATE
        int configID = 0;
        ByteBuffer bb = ByteBuffer.allocate(1024);

        while (msgBuff[0] != NAP.Message.MSG_SERVER_CONFIGURATION_COMPLETE.Value()) {
            msgLen = in.read(msgBuff);

            if (msgBuff[0] == NAP.Message.MSG_SERVER_CONFIGURATION_REQUEST.Value()) {

                configID = byteArrayToInt(msgBuff, 1);

                bb.put(msgBuff, 5, msgLen - 5);
                String str = bb.toString();
                Logger.getLogger(NAPClient.class).info("Config String: " + str);

                msgOutBuff.clear();
                msgOutBuff.put(NAP.Message.MSG_CLIENT_CONFIGURATION_ACK.Value());
                msgOutBuff.putInt(configID);
                out.write(msgOutBuff.array(), 0, msgOutBuff.position());
            }
        }
        
        msgOutBuff.clear();
        msgOutBuff.put(NAP.Message.MSG_CLIENT_CONFIGURATION_ACK.Value());
        msgOutBuff.putInt(configID);
        out.write(msgOutBuff.array(), 0, msgOutBuff.position());

        // END CONFIG
        
        // DATA STATE
        
        for (int x = 0; x<10; x++) {
            msgOutBuff.clear();
            msgOutBuff.put(NAP.Message.MSG_DATA_MESSAGE.Value());
            msgOutBuff.putInt(1);
            msgOutBuff.putInt(_clientID);
            msgOutBuff.putInt(_groupID);
            int dataId = (int)(Math.random()*1000);
            msgOutBuff.putInt(dataId);
            String data = "Sorted Data " + x;
            msgOutBuff.put(data.getBytes());
            
            out.write(msgOutBuff.array(), 0, msgOutBuff.position());
            
            msgLen = in.read(msgBuff);
            
            if (msgLen == NAP.Message.MSG_DATA_ACK.Length()
                  && msgBuff[0] == NAP.Message.MSG_DATA_ACK.Value()) {
            	if (dataId == byteArrayToInt(msgBuff, 1)){
            		Logger.getLogger(NAPClient.class).info("Client " + _clientID + " received data ack " + dataId);
            	} else {
            		Logger.getLogger(NAPClient.class).info("Client received incorrect data ack packet");
            	}
            }
        }
        // END DATA
        
        // SEND DISCONNECT

        msgOutBuff.clear();
        msgOutBuff.put(NAP.Message.MSG_DISCONNECT.Value());
        msgOutBuff.putInt(_clientID);
        msgOutBuff.putInt(_groupID);
        msgOutBuff.putInt(7);
        
        out.write(msgOutBuff.array(), 0, msgOutBuff.position());

    }

    public void disconnect() throws IOException
    {
	try
	{
            _sServer.close();
	}
	catch (IOException e)
	{
            System.err.println("Could not close the connection");
            e.printStackTrace();
            System.exit(-1);
	}
    }

    private static int byteArrayToInt(byte[] b, int offset) {
        int value = 0;
        for (int i = 0; i < 4; i++) {
            int shift = (4 - 1 - i) * 8;
            value += (b[i + offset] & 0x000000FF) << shift;
        }
        return value;
    }

    public int getServerVersion()
    {
        return _serverVersion;
    }

    public int getClientID()
    {
        return _clientID;
    }

    public int getGroupID()
    {
        return _groupID;
    }
    

}
